「艾草艾草,你在做什麼?」
艾草:「沒特別做什麼呀!」
「艾草艾草,我問你喔!」
艾草:「嗯嗯,問呀,但為什麼都要叫我名字兩次呀?」
「透過兩次的呼叫希望可以喚醒存在你心中的真善美,能使你成為更棒的存在!」
艾草:「??」
「上次在哪本魔法書看到的呀,好像某個術式要呼叫兩次什麼的,實驗一下!」
艾草:「啊,你說的應該是閉包吧?這個術式比較複雜,再陪你複習一次原理吧!」
以下引用至 MDN:
閉包(Closure)是函式以及該函式被宣告時所在的作用域環境(lexical environment)的組合。
這樣可能不理解,來看一段程式碼:
像這樣函式內 return
函式,且內層函式內並沒有 textContent
變數,需要透過範圍鍊找到外層的 textContent
變數來使用,就稱為閉包。
function outer(text) {
let textContent = text;
return function inner(name) {
console.log(`${textContent},${name}`); //"Hello,艾草"
};
}
閉包的呼叫要使用兩個小括號,只使用一個小括號時,會印出整個內層函式,第二個小括號會呼叫被 return
的函式,底下分別為使用一個及兩個小括號呼叫的印出結果:
一個小括號:
outer('Hello');
印出結果:
兩個小括號:
outer('Hello')('艾草');
印出結果:
接下來,閉包還可以這樣使用:
將變數賦予值為外層函式後,再透過變數呼叫內層函式,一樣可以成功印出。
function outer(text) {
let textContent = text;
return function inner(name) {
console.log(`${textContent},${name}`); //"Hello,艾草"
};
}
let sayHello = outer('Hello');
sayHello('艾草');
為什麼內層函式可以持續取到外層函式的變數呢?
首先,傳值、傳參考文章內有提到:每宣告一個變數,都會有相對應的記憶體空間存放變數。
而每個執行環境都會有對應的記憶體空間存放變數、函式,所以當我們執行到程式碼 let sayHello = outer('Hello')
時會跑去建立函式 outer
的執行環境,而執行到 let textContent = text
時,該執行環境內會產生記憶體空間來存放 textContent
變數與其值。
執行堆疊會如下圖:
接下來回到全域執行環境執行到 sayHello('艾草')
時,會進行以下幾個步驟:
outer
會離開執行環境sayHello()
, sayHello()
會指向函式 inner
inner
的執行環境,並於記憶體空間放置參數 name
inner
會需要參照外部函式 outer
的變數 textContent
而執行環境為了參考到外部變數 textContent
會將外部變數包覆起來,而這就是閉包。
JavaScript 會保留我們需要透過範圍鍊參照的外部變數,即使該變數所在執行環境已離開執行堆疊。
JavaScript 全攻略:克服 JS 的奇怪部分(Udemy)
JavaScript 核心篇(六角學院)
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Closures